iT邦幫忙

2026 iThome 鐵人賽

DAY 16
0
Claude AI

從零開始 Claude Code:30 天打造 AI 任務管理 Web App系列 第 16 篇

[Day 16] 任務不只新增刪除:開始管理狀態

  • 分享至 

  • xImage
  •  

Day 15 把 CRUD 補完整之後,目前任務已經可以:

新增
查看
編輯
刪除

但還有一個很常用的操作不夠方便。
例如任務目前是:

待處理

開始做之後想改成:

進行中

做完之後再改成:

已完成

Day 14 雖然可以進入編輯頁修改狀態,但如果只是要換一個狀態,每次都進完整編輯頁有點麻煩。
所以 Day 16 的目標是:
直接在首頁快速修改任務狀態。


今天的範圍

目前系統只有三種狀態:

todo
→ 待處理

in_progress
→ 進行中

done
→ 已完成

今天要做的是:

首頁選擇新狀態
    ↓
按「更新」
    ↓
POST 給 Flask
    ↓
修改 task.status
    ↓
寫回 MySQL
    ↓
回到首頁

今天先不做:

狀態篩選
分類篩選
分類 CRUD
services/
routes/
Claude API

首頁上方原本的:

全部
待處理
進行中
已完成

目前也還只是畫面,今天不處理它。


先讓 Claude Code 規劃

我先把需求交給 Claude Code:

目前狀態:

 - Task CRUD 已完成
 - 首頁會從 MySQL 顯示真正的 tasks
 - 每筆任務都有 todo / in_progress / done 狀態
 - Task Model 已有 STATUS_LABELS 和 STATUS_CSS_CLASSES
 - 編輯任務頁面可以修改 status
 - 首頁目前只能看到狀態,不能直接修改

今天只做:
「在首頁快速修改任務狀態」

需求:

1. 每張任務卡片增加一個狀態操作區
2. 可以直接選擇:
   - 待處理 todo
   - 進行中 in_progress
   - 已完成 done
3. 使用一般 HTML form + POST
4. 不使用 AJAX
5. 新增一個更新狀態的 Flask route
6. Flask 要根據 task_id 找到 Task
7. 找不到 Task 時回 404
8. 只允許 todo / in_progress / done
9. 更新 task.status 後 db.session.commit()
10. 完成後 redirect 回首頁
11. 原本的狀態 badge 和 task-completed 樣式要繼續正常作用

今天不要:
 - 做狀態篩選
 - 做分類篩選
 - 做分類 CRUD
 - 建立 services/
 - 建立 routes/
 - Claude API

請先規劃會修改哪些檔案,
不要直接修改程式。

最後只需要修改:

app.py
templates/index.html
static/css/style.css

不需要新增資料表,也不用新增新的 HTML 頁面。


app.py:新增狀態更新 Route

Day 16 新增:

@app.route("/tasks/<int:task_id>/status", methods=["POST"])
def update_status(task_id):
    task = Task.query.get_or_404(task_id)

    new_status = request.form.get("status")
    if new_status not in Task.STATUS_LABELS:
        abort(400)

    task.status = new_status
    db.session.commit()

    return redirect(url_for("index"))

這條 Route 只負責:

更新 status

不會碰其他欄位。


task_id 還是用來找指定任務

例如:

task.id = 3

首頁送出:

POST /tasks/3/status

Flask 就會收到:

task_id = 3

接著:

task = Task.query.get_or_404(task_id)

去資料庫找:

id = 3

的任務。

如果找不到,就直接回:

404 Not Found

這跟 Day 14 編輯、Day 15 刪除的做法一致。


為什麼要驗證 status?

首頁下拉選單雖然只會提供:

todo
in_progress
done

但後端還是不能完全相信前端送來的值。
所以我用了:

new_status = request.form.get("status")

if new_status not in Task.STATUS_LABELS:
    abort(400)

目前 Task Model 已經有:

STATUS_LABELS = {
    "todo": "待處理",
    "in_progress": "進行中",
    "done": "已完成",
}

所以它的 key 正好就是目前允許的三種狀態。
合法

todo
in_progress
done

如果有人自己送:

abc

就會:

400 Bad Request

而不會把奇怪的值寫進資料庫。


為什麼還是用 POST?

這次是在修改資料庫內容,所以不應該用 GET。
目前專案還是一般:

Flask
  +
Jinja2
  +
HTML form

沒有 AJAX
所以使用:

methods=["POST"]

最簡單。
流程就是:

選新狀態
  ↓
按更新
  ↓
POST
  ↓
Flask
  ↓
MySQL

index.html:首頁加入狀態下拉選單

每張任務卡片現在多了一個:

<form
    class="status-form"
    method="POST"
    action="{{ url_for('update_status', task_id=task.id) }}">

    <select name="status">
        <option value="todo"
            {% if task.status == 'todo' %}selected{% endif %}>
            待處理
        </option>

        <option value="in_progress"
            {% if task.status == 'in_progress' %}selected{% endif %}>
            進行中
        </option>

        <option value="done"
            {% if task.status == 'done' %}selected{% endif %}>
            已完成
        </option>
    </select>

    <button class="status-update-button" type="submit">
        更新
    </button>
</form>

現在首頁就可以直接選擇狀態後,再按「更新」。
https://ithelp.ithome.com.tw/upload/images/20260930/20183857v7FLRxLhRr.png


selected 讓目前狀態保留下來

例如資料庫目前:

status = in_progress

這段:

{% if task.status == 'in_progress' %}selected{% endif %}

就會讓下拉選單重新整理後顯示:

進行中

所以畫面上的狀態不會每次都回到第一個「待處理」。


狀態更新後,Badge 不用另外改

Day 13 已經有:

<span class="status-badge {{ task.status_css_class }}">
    {{ task.status_label }}
</span>

而 Task Model 有:

STATUS_LABELS = {
    "todo": "待處理",
    "in_progress": "進行中",
    "done": "已完成",
}

以及:

STATUS_CSS_CLASSES = {
    "todo": "status-todo",
    "in_progress": "status-progress",
    "done": "status-done",
}

所以只要資料庫裡:

status

改了,首頁重新讀資料之後,Badge 就會一起更新。
例如:

todo
→ 待處理

in_progress
→ 進行中

done
→ 已完成

CSS 顏色也會跟著變。


已完成的劃線效果也會自動生效

首頁本來就有:

<article class="task-card{% if task.status == 'done' %} task-completed{% endif %}">

所以當狀態變成:

done

就會套用:

task-completed

原本的 CSS:

.task-completed h2 {
    color: var(--text-secondary);
    text-decoration: line-through;
}

會讓任務標題出現劃線。
所以 Day 16 不需要另外重寫這部分。


CSS:加入狀態操作區

這次新增:

.status-form {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-top: 8px;
}

讓:

下拉選單 + 更新按鈕

排在一起。
下拉選單:

.status-form select {
    padding: 5px 8px;
    border: 1px solid var(--border);
    border-radius: 6px;
    font: inherit;
    font-size: 0.85rem;
    color: var(--text-primary);
    background: var(--surface);
}

更新按鈕:

.status-update-button {
    padding: 5px 10px;
    border: 1px solid var(--border);
    border-radius: 6px;
    background: var(--surface);
    color: var(--text-secondary);
    font-size: 0.85rem;
    font-weight: 600;
}

沒有重新設計整個首頁,只補上這次需要的操作。


測試時遇到一個 <select> 小問題

第一次測試時,我打開狀態下拉選單,發現原生 <select> 的選項位置會往左下偏。
問題不是 Flask,也不是狀態表單本身。
原本卡片有:

.task-card:hover {
    transform: translateY(-2px);
    box-shadow: 0 8px 24px rgba(31, 41, 55, 0.08);
}

滑鼠點 <select> 時,本來就會停在卡片上,所以卡片同時進入 hover。
這時:

transform: translateY(-2px);

會干擾 Chrome 系瀏覽器原生 <select> 的下拉選單定位。
https://ithelp.ithome.com.tw/upload/images/20260930/20183857e7R0iQYABf.png
所以最後把 transform 拿掉。


修正卡片 hover

原本:

.task-card {
    transition: transform 160ms ease, box-shadow 160ms ease;
}

.task-card:hover {
    transform: translateY(-2px);
    box-shadow: 0 8px 24px rgba(31, 41, 55, 0.08);
}

修改成:

.task-card {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
    gap: 32px;
    padding: 22px 24px;
    border: 1px solid var(--border);
    border-radius: 12px;
    background: var(--surface);
    box-shadow: 0 4px 16px rgba(31, 41, 55, 0.04);
    transition: box-shadow 160ms ease;
}

.task-card:hover {
    box-shadow: 0 8px 24px rgba(31, 41, 55, 0.08);
}

現在 hover 還是會有比較明顯的陰影,所以視覺效果還在。
但不再移動整張卡片,也就不會影響 <select> 的位置。


實際畫面

目前首頁已經可以直接看到:
https://ithelp.ithome.com.tw/upload/images/20260930/20183857TiiiR2NQMr.png


MySQL 也同步更新

最後回到 MySQL Workbench:

SELECT * FROM tasks;

確認該筆任務的:

status

真的跟著變成:

todo

或:

in_progress

或:

done

這樣才算真的完成:

首頁
 ↓
POST
 ↓
Flask
 ↓
SQLAlchemy
 ↓
MySQL

整條流程。


Day 16 做完後

現在任務不需要每次進編輯頁才能換狀態。
可以直接從首頁:

待處理
  ↓
進行中
  ↓
已完成

而且狀態變更會同步影響:

MySQL status
首頁下拉選單
狀態 Badge
Badge 顏色
完成任務劃線

目前首頁上方:

全部
待處理
進行中
已完成

還沒有真的篩選功能。
Day 17 才要讓這些按鈕開始真的有作用,並加入分類篩選。


上一篇
[Day 15] 把基本 CRUD 補完整:加入刪除任務
下一篇
[Day 17] 任務開始變多:加入分類與篩選
系列文
從零開始 Claude Code:30 天打造 AI 任務管理 Web App 共 17 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言